<?php
/**
 * Created by PhpStorm.
 * Author: sitenv@aliyun.com
 * CreateTime: 2021/11/12 16:25
 * Blog：www.myblogs.xyz
 */

namespace app\api\controller;


use app\common\model\Setting;

class WxPay
{
    protected $appId = ''; // 微信应用AppId
    protected $mchId = ''; // 商户号
    protected $key = ''; // 商户API秘钥
    protected $error = ''; // 错误信息
    protected $httpUrl = 'https://api.mch.weixin.qq.com';

    public function __construct($type = 'wx_pay')
    {
        $setting_model = new Setting();
        $setting_data = $setting_model->settingFind($type);
        $this->appId = $setting_data['app_id'];
        $this->mchId = $setting_data['mch_id'];
        $this->key = $setting_data['key'];
    }

    // APP支付（APP）
    public function wxAppPay($param = [])
    {
        // wx_apy
        return $this->into('app', $param);
    }

    // 微信公众号/小程序支付（JS API）
    public function wxJsApiPay($param)
    {
        // wx_public
        return $this->into('jsapi', $param);
    }

    // H5、APP/公众号（H5）
    public function wxH5Pay($param)
    {
        // wx_public
        return $this->into('h5', $param);
    }

    // 扫码支付、APP/公众号/小程序（Native）
    public function wxNativePay($param)
    {
        // wx_applet
        return $this->into('native', $param);
    }

    // 企业付款到零钱
    public function wxBusinessPay($param)
    {
        if (empty($param['openid'])){
            $this->error = '缺少openid';
            return false;
        }
        // 随机字符串
        $nonce_str = $this->getNonceStr();
        // 发送数据
        $post_data = [
            'mch_appid' => $this->appId,
            'mchid' => $this->mchId,
            'nonce_str' => $nonce_str,
            'partner_trade_no' => $param['order_code'],
            'openid' => $param['openid'],
            'check_name' => 'NO_CHECK',
            'amount' => floatval($param['money']) * 100,
            'desc' => $param['body'],
            'spbill_create_ip' => get_client_ip()
        ];
        // 生成签名
        $post_data['sign'] = $this->MakeSign($post_data, $this->key);
        // 组合XML数据
        $xmlData = $this->MakeXml($post_data);
        // 发送请求
        $url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
        $result = $this->curlPost($url, $xmlData);
        // 解析xml数据
        $payment = $this->FromXml($result);
        // 是否请求成功
        if($payment['return_code'] != 'SUCCESS'){
            $this->error = $payment['return_msg'];
            return false;
        }
        if ($payment['result_code'] == 'SUCCESS'){
            return true;
        }
        return false;
    }

    /**
     * 微信订单退款
     * @param $order_code string 商家退款订单号
     * @param $trade_code string 微信支付订单号
     * @param $total double 订单金额
     * @param $money double 退款金额
     * @param string $reason
     * @return bool
     */
    public function wxRefund($order_code, $trade_code, $total, $money, $reason = '押金退款')
    {
        // 随机字符串
        $nonce_str = $this->getNonceStr();
        // 发送数据
        $post_data = [
            'appid'          => $this->appId,
            'mch_id'         => $this->mchId,
            'nonce_str'      => $nonce_str,
            'out_trade_no'   => $trade_code,
            'out_refund_no'  => $order_code,
            'total_fee'      => floatval($total) * 100,
            'refund_fee'     => floatval($money) * 100,
            'refund_desc'    => $reason
        ];
        // 生成签名
        $post_data['sign'] = $this->MakeSign($post_data, $this->key);
        // 组合XML数据
        $xmlData = $this->MakeXml($post_data);
        // 发送请求
        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
        $result = $this->curlPost($url, $xmlData, true);
        // 解析xml数据
        $payment = $this->FromXml($result);
        // 是否请求成功
        if($payment['return_code'] != 'SUCCESS'){
            $this->error = $payment['return_msg'];
            return false;
        }
        if ($payment['result_code'] == 'SUCCESS'){
            sleep(2);
            $rows = $this->wxRefundQuery($payment['refund_id']);
            if ($rows == 'SUCCESS'){
                return true;
            }
        }
        return false;
    }

    /**
     * 查询退款订单
     * @param string $order_code 查询单号
     * @param int $priority 查询优先级
     * @return bool
     */
    public function wxRefundQuery($order_code, $priority = 0)
    {
        // 随机字符串
        $nonce_str = $this->getNonceStr();
        // 订单退款信息
        $post_data = [
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => $nonce_str,
        ];
        // 微信订单号查询的优先级是： refund_id > out_refund_no > transaction_id > out_trade_no
        // 微信退款单号 > 商户退款单号 > 微信订单号 > 商户订单号
        $query = ['refund_id', 'out_refund_no', 'transaction_id', 'out_trade_no'];
        $post_data[$query[$priority]] = $order_code;
        // 生成签名
        $post_data['sign'] = $this->MakeSign($post_data, $this->key);
        // 组合XML数据
        $xmlData = $this->MakeXml($post_data);
        // 发送请求
        $url = 'https://api.mch.weixin.qq.com/pay/refundquery';
        $result = $this->curlPost($url, $xmlData, true);
        // 解析xml数据
        $payment = $this->FromXml($result);
        //判断是否请求成功
        if ($payment['return_code'] != 'SUCCESS') {
            return false;
        }
        // 判断是否支付
        if ($payment['result_code'] == 'SUCCESS') {
            return $payment['trade_state'];
        }
        return false;
    }

    /**
     * 订单查询
     * @param $order_code
     * @param int $priority
     * @return bool
     */
    public function orderQuery($order_code, $priority = 0)
    {
        // 随机字符串
        $nonce_str = $this->getNonceStr();
        // 订单退款信息
        $post_data = [
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => $nonce_str,
        ];
        // 微信订单号查询的优先级是： refund_id > out_refund_no > transaction_id > out_trade_no
        // 微信退款单号 > 商户退款单号 > 微信订单号 > 商户订单号
        $query = ['refund_id', 'out_refund_no', 'transaction_id', 'out_trade_no'];
        $post_data[$query[$priority]] = $order_code;
        // 生成签名
        $post_data['sign'] = $this->MakeSign($post_data, $this->key);
        // 组合XML数据
        $xmlData = $this->MakeXml($post_data);
        // 发送请求
        $url = 'https://api.mch.weixin.qq.com/pay/orderquery';
        $result = $this->curlPost($url, $xmlData, true);
        // 解析xml数据
        $payment = $this->FromXml($result);
        //判断是否请求成功
        if ($payment['return_code'] != 'SUCCESS') {
            return false;
        }
        // 判断是否支付
        if ($payment['result_code'] == 'SUCCESS') {
            return $payment['trade_state'];
        }
        return false;
    }

    /**
     * 统一下单
     * @param $type
     * @param array $param
     * @return bool|mixed
     */
    private function into($type, $param = [])
    {
        if (empty($param)) return false;
        // 随机字符串
        $nonce_str = $this->getNonceStr();
        // 发送数据
        $post_data = [
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => $nonce_str,
            'body' => $param['body'],
            'out_trade_no' => $param['order_code'],
            'total_fee' => floatval($param['money']) * 100,
            'spbill_create_ip' => get_client_ip(),
            'notify_url' => $param['notify_url'],
        ];
        if ($type == 'app'){
            $post_data['device_info'] = 'web';
            $post_data['trade_type'] = 'APP';
        }
        if ($type == 'jsapi' || $type == 'h5'){
            if (empty($param['openid'])){
                $this->error = '缺少openid';
                return false;
            }
            $post_data['openid'] = $param['openid'];
            $post_data['trade_type'] = 'JSAPI';
        }
        // 生成签名
        $post_data['sign'] = $this->MakeSign($post_data, $this->key);
        // 组合XML数据
        $xmlData = $this->MakeXml($post_data);
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $result = $this->curlPost($url, $xmlData);
        // 解析xml数据
        $payment = $this->FromXml($result);
        // 是否请求成功
        if($payment['return_code'] != 'SUCCESS'){
            $this->error = $payment['return_msg'];
            return false;
        }
        if ($payment['result_code'] == 'SUCCESS'){
            $payment['timestamp'] = NEW_TIME;
            if ($type == 'app'){
                $sign_data = [
                    'appid'=>$this->appId,
                    'partnerid'=>$this->mchId,
                    'prepayid'=>$payment['prepay_id'],
                    'package'=>'Sign=WXPay',
                    'noncestr'=>$nonce_str,
                    'timestamp'=>(string)$payment['timestamp']
                ];
                $sign_data['sign'] = $this->MakeSign($sign_data , $this->key);
            }else{
                $sign_data = [
                    'appId'=>$this->appId,
                    'nonceStr'=>$nonce_str,
                    'package'=>'prepay_id=' . $payment['prepay_id'],
                    'signType'=>'MD5',
                    'timeStamp'=>$payment['timestamp']
                ];
                $sign_data['paySign'] = $this->MakeSign($sign_data , $this->key);
            }
            return $sign_data;
        }
        $this->error = $param['body'] . '失败';
        return false;
    }

    // 获取错误内容
    public function getError()
    {
        return $this->error;
    }

    /**
     * 生成签名 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     * @param $sign
     * @param string $keys
     * @return string
     */
    protected function MakeSign($sign, $keys = '')
    {
        ksort($sign); // 数组排序
        $str = '';
        foreach($sign as $key=>$val){
            if($val != ''){
                $str .= $key . "=" . $val . "&";
            }
        }
        $str .= "key=" . $keys;
        $sign = strtoupper(md5($str));
        return $sign;
    }

    /**
     * 生成XML数据
     * @param $data
     * @return string
     */
    protected function MakeXml($data)
    {
        $xmlData = "<xml>";
        foreach($data as $key=>$val){
            $xmlData.="<".$key.">".$val."</".$key.">";
        }
        $xmlData.= "</xml>";
        return $xmlData;
    }

    /**
     * 将xml转为array
     * @param $xml
     * @return mixed
     */
    protected function FromXml($xml)
    {
        if(!$xml){
            $this->jsonReturn(0,'','XML解析错误！');
        }
        libxml_disable_entity_loader(true);
        $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $values;
    }

    /**
     * 产生随机字符串，不长于32位
     * @param int $length
     * @return string
     */
    private function getNonceStr($length = 32)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
        $str = '';
        for ( $i = 0; $i < $length; $i++ )  {
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }

    /**
     * POST请求数据
     * @param $url
     * @param $xmlData
     * @param bool $useCert
     * @return mixed
     */
    public function curlPost($url,$xmlData, $useCert = false)
    {
        $header[] = "Content-type: text/xml";
        // POST发送数据
        $ch = curl_init(); // 初始化CURL会话
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        if($useCert == true){
            //设置证书
            //使用证书：cert 与 key 分别属于两个.pem文件
            //证书文件请放入服务器的非web目录下
            $sslCertPath = getcwd() . '/'. $this->mchId .'_cert/apiclient_cert.pem';
            $sslKeyPath = getcwd() . '/'. $this->mchId .'_cert/apiclient_key.pem';
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY, $sslKeyPath);
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlData);
        $result = curl_exec($ch); // 获取结果
        curl_close($ch);// 关闭会话
        return $result;
    }

}